#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <elf.h>
#include <errno.h>

/**************** CONFIG OPTIONS ****************/
#define DMESG_FILE	"dmesg.txt"
#define ELFHEAD_FILE	"elf.head"
#define CPUREGS_FILE	"cpuregs.txt"

unsigned long paddr_base = 0x1000000;
unsigned long paddr_size = 0x7f000000;
unsigned long vaddr_base = 0xffffff8008000000;
unsigned long crash_info = 0xc040000;
char dumpfile[64] = {'c', 'o', 'r', 'e', };
/************************************************/
#define PAGE_SIZE	4096

#define RAMD_PAGE_SIZE	4096
#define RAMD_HEAD_SIZE	2048
#define RAMD_INFO_SIZE	(RAMD_PAGE_SIZE - RAMD_HEAD_SIZE)
struct crash_page {
	union {
		struct {
#define RAMD_MAGIC	0x52414d44	/* "RAMD" */
			uint32_t indicator;
#define RAMD_ON_EMMC	1
#define RAMD_ON_USB	2
			uint32_t dump_style;
			uint64_t dram_start;
			uint64_t dram_end;
			uint64_t regs[31];
			uint64_t sp;
			uint64_t pc;
			uint64_t pstate;
			uint64_t ttbr1_el1;
			uint64_t ttbr0_el1;
			uint64_t tpidr_el1;
			uint64_t vbar_el1;
			uint64_t esr_el1;
			uint64_t far_el1;
			uint64_t tcr_el1;
			uint64_t mair_el1;
			uint32_t midr_el1;
			uint32_t revidr_el1;
			uint32_t sctlr_el1;
			uint32_t actlr_el1;
			uint32_t cpacr_el1;
			uint32_t isr_el1;
		} info;
		char padding0[RAMD_INFO_SIZE];
	};
	union {
#ifndef __UBOOT__ /* uboot doesn't support elf64 format */
		struct {
			struct elfhdr ehdr;
#define RAMD_PHDR_NUM	2
			struct elf_phdr ephdrs[RAMD_PHDR_NUM];
			uint64_t notes[0];
		} elf_header;
#endif
		char padding1[RAMD_HEAD_SIZE];
	};
};

unsigned long vaddr_to_offset(unsigned long vaddr)
{
	if ((vaddr & 0xffffffc000000000ul) == 0xffffffc000000000ul)
		return vaddr - 0xffffffc000000000 - paddr_base;
	if ((vaddr & 0xffffffc000000000) == 0xffffff8000000000)
		return vaddr - vaddr_base;
	printf("can't determine vaddr %lx, exit..\n", vaddr);
	exit(EXIT_FAILURE);
}

unsigned long paddr_to_offset(unsigned long paddr)
{
	return paddr - paddr_base;
}

void parse_arg(int argc, char *const *argv)
{
	int opt;
	while ((opt = getopt(argc, argv, "m:p:v:c:")) != -1) {
		switch (opt) {
			case 'm':
				memcpy(dumpfile, optarg, 1 + strlen(optarg));
				break;
			case 'p':
				paddr_base = strtol(optarg, NULL, 16);
				break;
			case 'v':
				vaddr_base = strtol(optarg, NULL, 16);
				break;
			case 'c':
				crash_info = strtol(optarg, NULL, 16);
				break;
			default: /* '?' */
				fprintf(stderr, "Usage: %s [-m memdump] [-p] paddr"
					" [-v] vaddr [-c] crash_page\n", argv[0]);
				exit(EXIT_FAILURE);
		}
	}
}


static inline int __parse_helper(char *buf, char *mark, uint64_t *var)
{
	void *ptr;

	ptr = memmem(buf, RAMD_HEAD_SIZE, mark, strlen(mark));
	if (!ptr) {
		printf("\n[-] can't find %s\n", mark);
		return -1;
	}
	ptr += strlen(mark);
	sscanf(ptr, "%lx", var);
	return 0;
}

static inline int __getvar_helper(int fd, char *buf, char *mark, uint64_t *var)
{
	unsigned long tmp;
	loff_t pos;
	void *ptr;

	ptr = memmem(buf, RAMD_HEAD_SIZE, mark, strlen(mark));
	if (!ptr) {
		printf("\n[-] can't find symbol %s!\n", mark);
		return -1;
	}
	ptr += strlen(mark);
	sscanf(ptr, "%lx", &tmp);
	pos = lseek(fd, vaddr_to_offset(tmp), SEEK_SET);
	if (pos < 0) {
		printf("\n[-] lseek failed\n");
		return -1;
	}
	tmp = read(fd, var, sizeof(var));
	if (tmp < sizeof(var))
		return -1;
	return 0;
}

int get_dmesg(int dump_fd, struct crash_page *crash, char *filename)
{
	uint64_t log_buf;
	uint64_t log_buf_len;
	uint64_t log_first_idx;
	uint64_t log_next_idx;
	uint64_t size_printk_log;
	uint64_t off_ts_nsec;
	uint64_t off_len;
	uint64_t off_text_len;
	uint64_t off_dict_len;

	loff_t pos;
	uint64_t log_cur_idx;
	char buf[PAGE_SIZE];
	void *log;
	void *ptr;
	int fd_write;
	long count;
	int ret = 0;

#define MARK_LOG_BUF		"SYMBOL(log_buf)="
#define MARK_LOG_BUF_LEN	"SYMBOL(log_buf_len)="
#define MARK_LOG_FIRST_IDX	"SYMBOL(log_first_idx)="
#define MARK_LOG_NEXT_IDX	"SYMBOL(log_next_idx)="

#define MARK_SIZE_PRINTK_LOG	"SIZE(printk_log)="
#define MARK_OFF_TS_NSEC	"OFFSET(printk_log.ts_nsec)="
#define MARK_OFF_LEN		"OFFSET(printk_log.len)="
#define MARK_OFF_TEXT_LEN	"OFFSET(printk_log.text_len)="
#define MARK_OFF_DICT_LEN	"OFFSET(printk_log.dict_len)="

	memcpy(buf, crash->padding1, RAMD_HEAD_SIZE);
	__getvar_helper(dump_fd, buf, MARK_LOG_BUF, &log_buf);
	__getvar_helper(dump_fd, buf, MARK_LOG_BUF_LEN, &log_buf_len);
	__getvar_helper(dump_fd, buf, MARK_LOG_FIRST_IDX, &log_first_idx);
	__getvar_helper(dump_fd, buf, MARK_LOG_NEXT_IDX, &log_next_idx);
	__parse_helper(buf, MARK_SIZE_PRINTK_LOG, &size_printk_log);
	__parse_helper(buf, MARK_OFF_TS_NSEC, &off_ts_nsec);
	__parse_helper(buf, MARK_OFF_LEN, &off_len);
	__parse_helper(buf, MARK_OFF_TEXT_LEN, &off_text_len);
        __parse_helper(buf, MARK_OFF_DICT_LEN, &off_dict_len);
#if 0
	{
	int i;
	for (i = 0; i < PAGE_SIZE; i++)
		printf("%c", buf[i]);
	printf("log_buf=%lx\n", log_buf);
	printf("log_buf_len=%lx\n", log_buf_len);
	printf("log_first_idx=%lx\n", log_first_idx);
	printf("log_next_idx=%lx\n", log_next_idx);

	}
#endif

	pos = lseek(dump_fd, vaddr_to_offset(log_buf), SEEK_SET);
	if (pos < 0) {
		printf("\n[-] lseek failed(crash->info.vmcoreinfo_note)\n");
		return -1;
	}
	fd_write = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
	if (fd_write < 0) {
		printf("\n[-] can't create %s\n", filename);
		return -1;
	}
	log = malloc(log_buf_len);
	if (!log) {
		printf("\n[-] short of memory!\n");
		goto out;
	}
	count = read(dump_fd, log, log_buf_len);
	if (count < log_buf_len) {
		printf("\n[-] read failed.%ld/%ld (errno=%d)\n",
			count, log_buf_len, errno);
		ret = errno;
		goto free;
	}
	log_cur_idx = log_first_idx;
	count = 0;
	while (log_cur_idx != log_next_idx) {
		void *record = log_cur_idx + log;
		uint64_t *nsec = record + off_ts_nsec;
		int16_t *len = record + off_len;
		int16_t *text_len = record + off_text_len;
		*(char*)(record + *len) = '\0';
		sprintf(buf, "[%6ld.%06ld] %s\n",
			*nsec/1000000, *nsec%1000000, (char*) record + size_printk_log);
		log_cur_idx += *len;
		if (log_cur_idx >= log_buf_len) {
			log_cur_idx -= log_buf_len;
		}
		count += write(fd_write, buf, strlen(buf));
		if (count > log_buf_len * 2) /* can't be */
			return -1;
	}

free:
	free(log);
out:
	close(fd_write);

	return ret;
}

int get_elfhead(int dump_fd, struct crash_page *crash, char *filename)
{
	int fd;
	(void) dump_fd;
	long count;

	fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
	if (fd < 0) {
		printf("\n[-] can't create %s\n", filename);
		return -1;
	}
	count = write(fd, crash->padding1, RAMD_HEAD_SIZE);
	if (count < RAMD_HEAD_SIZE)
		return -1;
	close(fd);
	return 0;
}

int get_cpuregs(int dump_fd, struct crash_page *crash, char *filename)
{
	int fd;
	(void) dump_fd;
	long count;
	int i;
	uint64_t *regs_base;
	char buf[128];
	int c;

	fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
	if (fd < 0) {
		printf("\n[-] can't create %s\n", filename);
		return -1;
	}
//	regs_base = (void *)(crash->info.paddr_cpuregs - (uint64_t) crash_info +
//			(int64_t) crash);
	for (i = 0; i < 31; i++) {
		if ((i > 0) && (i % 4 == 0))
			c = '\n';
		else
			c = ' ';
		snprintf(buf, 128, "R%02d=%016lx%c", i, regs_base[i], c);
		count = write(fd, buf, strlen(buf));
		if (count < strlen(buf))
			return -1;
	}
	snprintf(buf, 128, "\nSP=%016lx\n", regs_base[31]);
	count = write(fd, buf, strlen(buf));
	if (count < strlen(buf))
		return -1;
	snprintf(buf, 128, "PC=%016lx\n", regs_base[32]);
	count = write(fd, buf, strlen(buf));
	if (count < strlen(buf))
		return -1;
	snprintf(buf, 128, "PSTATE=%016lx\n", regs_base[33]);
	count = write(fd, buf, strlen(buf));
	if (count < strlen(buf))
		return -1;

	close(fd);
	return 0;
}

int main(int argc, char *argv[])
{
	int fd;
	int ret;
	loff_t pos;
	struct crash_page *crash;
	long count;

	parse_arg(argc, argv);

	fd = open(dumpfile, O_RDONLY);
	if (fd < 0) {
		printf("[-] can't open %s!\n", dumpfile);
		return -1;
	}
	crash = malloc(RAMD_PAGE_SIZE);
	if (!crash) {
		printf("[-] short of memory!\n");
		return -1;
	}

	printf("[*] --- ASR crash tools ---\n");

	pos = lseek(fd, paddr_to_offset(crash_info), SEEK_SET);
	count = read(fd, crash, RAMD_PAGE_SIZE);
	if (count < 0)
		return -1;
	printf("[*] generate dmesg file: %s.", DMESG_FILE);
	ret = get_dmesg(fd, crash, DMESG_FILE);
	if (ret == 0)
		printf("\t\tDONE\n");
	printf("[*] generate elf head: %s.", ELFHEAD_FILE);
	ret = get_elfhead(fd, crash, ELFHEAD_FILE);
	if (ret == 0)
		printf("\t\tDONE\n");
//	printf("[*] generate cpu registers: %s.", CPUREGS_FILE);
//	ret = get_cpuregs(fd, crash, CPUREGS_FILE);
//	if (ret == 0)
//		printf("\t\tDONE\n");

	free(crash);
	close(fd);

	return 0;
}
